﻿/*****************************************************************************\
 *             University of Illinois/NCSA Open Source License
 *
 * 
 *      Copyright © 2012,2013, The Board of Trustees of the University of Illinois. All rights reserved.
 *
 *      Developed by: Rakesh Bobba, Derek Dagit, Erich Heine, Denis Kholine,
 *                    Klara Nahrstedt, David Rodgers,   William H. Sanders, Tim Yardley
 *
 *      Information Trust Institute/Coordinated Science Laboratory
 * 
 *      University of Illinois
 *      
 *      The Board of Trustees of the University of Illinois
 *      109 Coble Hall
 *      801 South Wright Street
 *      Champaign, IL 61820-6242
 *
 *      http://www.iti.illinois.edu / http://csl.illinois.edu
 * 
 *      Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
 *      associated documentation files (the “Software”), to deal with the Software without restriction, 
 *      including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *      and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
 *      subject to the following conditions:
 *
 *      Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *      following disclaimers.
 *      
 *      Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
 *      the following disclaimers in the documentation and/or other materials provided with the distribution.
 *
 *      Neither the names of Information Trust institute, Coordinated Science Laboratory, University of Illinois,
 *      The Board of Trustees of the University of Illinois nor the names of its contributors may be used to 
 *      endorse or promote products derived from this Software without specific prior written permission.
 *      
 *      THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 *      LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN
 *      NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *      WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 *      SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
 *      
 \*****************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace SIEGate.Core.MessageBusses
{
    public class ChannelMessageBusProxy : IMessageBusProxy
    {
        private ChannelMessageBus mb;
        private HashSet<Guid> m_subscribed = new HashSet<Guid>();

        internal volatile List<ChannelMessageBusConsumer> Consumers = new List<ChannelMessageBusConsumer>();
        internal ChannelMessageBusProducer Producer;
        private int m_roundRobinSpot;
        public int QueryLoops = 20;

        public event EventHandler<MessageBusSubscriptionEventArgs> StartSubscription;
        public event EventHandler<MessageBusSubscriptionEventArgs> EndSubscription;

        public ISet<Guid> Published
        {
            get
            {
                return Producer == null ? new HashSet<Guid>() : Producer.StreamIDs;
            }
        }
        public ISet<Guid> ActivePubs
        {
            get
            {
                return Producer.ConsumedStreamIDs;
            }
        }
        public ISet<Guid> Subscribed
        {
            get
            {
                return m_subscribed;
            }
        }

        public void Subscribe(ISet<Guid> neededMessages)
        {
            //because concurrent

            List<ChannelMessageBusConsumer> newConsumersList = new List<ChannelMessageBusConsumer>(Consumers);
            HashSet<Guid> haveProvidersFor = new HashSet<Guid>();
            foreach (ChannelMessageBusConsumer c in newConsumersList)
            {
                foreach (Guid g in c.Subscribe(neededMessages))
                {
                    haveProvidersFor.Add(g);
                }
            }
            // consumers are created with subs already in place.
            m_subscribed.UnionWith(neededMessages);
            HashSet<Guid> needProvidersFor = new HashSet<Guid>(neededMessages.Except(haveProvidersFor));
            List<ChannelMessageBusConsumer> createdConsumers = mb.GetConsumers(needProvidersFor);
            foreach (ChannelMessageBusConsumer c in createdConsumers)
            {
                c.Subscribe(needProvidersFor);
            }
            newConsumersList.AddRange(createdConsumers);
            Consumers = newConsumersList;
        }

        public void UnSubscribe(ISet<Guid> toRemove)
        {
            foreach (ChannelMessageBusConsumer c in Consumers)
            {
                c.Unsubscribe(toRemove);
            }
            m_subscribed.ExceptWith(toRemove);
        }

        public void Publish(ISet<Guid> streamIDs)
        {
            if (Producer == null)
            {
                Producer = mb.GetProducer(streamIDs, new QOSParams());
                Producer.StartSubscription += producer_StartSubscription;
                Producer.EndSubscription += producer_EndSubscription;
            }
            // note the asymmetry with consumer: because of subscribe before publish, we need to manually 
            // publish instead of letting the constructor do it, no matter what. The reson is that all the callbacks
            // for events need to be set before publish works right.

            Producer.Publish(streamIDs);

        }

        void producer_EndSubscription(object sender, MessageBusSubscriptionEventArgs e)
        {
            OnEndSubscription(e.StreamID);
        }

        void producer_StartSubscription(object sender, MessageBusSubscriptionEventArgs e)
        {
            OnStartSubscription(e.StreamID);
        }

        public void UnPublish(ISet<Guid> toRemove)
        {
            throw new NotImplementedException("we don't have unpublish yet");
        }

        public void Send(params IMeasurement[] m)
        {
            if (Producer == null) { throw new InvalidOperationException("no producer"); }
            Producer.Send(m);
        }

        public IMeasurement[] Recv(int max)
        {
            // Because concurrent
            List<ChannelMessageBusConsumer> localConsumers = Consumers;

            List<IMeasurement> res;
            SpinWait spinner;
            int loops;

            if (!localConsumers.Any())
            {
                Thread.Sleep(1);
                return new IMeasurement[0];
            }

            res = new List<IMeasurement>();
            spinner = default(SpinWait);
            loops = 0;

            while (loops < QueryLoops)
            {
                // TODO: send a notification or something on end of publish, or this
                // will block forever
                /*if (Consumers.Count() == 1)
                {
                    return Consumers.First().RecvMany(max, true);
                }*/

                foreach (ChannelMessageBusConsumer consumer in localConsumers.Skip(m_roundRobinSpot))
                {
                    res.AddRange(consumer.RecvMany(max - res.Count));
                    m_roundRobinSpot++;

                    if (res.Count > max)
                        break;
                }

                m_roundRobinSpot = m_roundRobinSpot % localConsumers.Count;

                if (!res.Any())
                    spinner.SpinOnce();
                else
                    break;

                loops++;
            }

            return res.ToArray();

        }

        internal void OnStartSubscription(Guid sid)
        {
            if (StartSubscription != null)
            {
                StartSubscription(this, new MessageBusSubscriptionEventArgs(sid));
            }
        }
        internal void OnEndSubscription(Guid sid)
        {
            if (EndSubscription != null)
            {
                EndSubscription(this, new MessageBusSubscriptionEventArgs(sid));
            }

        }
        internal ChannelMessageBusProxy(ChannelMessageBus mb)
        {
            this.mb = mb;
            m_roundRobinSpot = 0;
        }
    }
}
